﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using SuperSerialization.Tree;
using System.Diagnostics;

namespace SuperSerialization.TreeVisitor
{
    /// <summary>
    /// This visitor traverses a document tree built by a parser and attempts to construct a corresponding in-memory object instance.
    /// </summary>
    class DeserializationVisitor : AbstractVisitor
    {
        /// <summary>
        /// The resulting object of this visitor.
        /// </summary>
        public object Object { get; protected set; }

        /// <summary>
        /// Gets the current object on the top of the stack, if any, i.e., the object currently be modified.
        /// </summary>
        protected object currentObject { get { if (objects.Count > 0) return objects.Peek(); else return null; } }

        /// <summary>
        /// The stack of objects being modified.
        /// </summary>
        protected Stack<object> objects = new Stack<object>();

        /// <summary>
        /// The stack of array elements if an array is currently being constructed.
        /// </summary>
        protected Stack<object> arrayElements = new Stack<object>();

        /// <summary>
        /// The stack of fields if an object is currently under construction.
        /// </summary>
        protected Stack<FieldInfo> fields = new Stack<FieldInfo>();
        
        /// <summary>
        /// A list of namespace declarations.
        /// </summary>
        protected List<string> namespaces = new List<string>();

        /// <summary>
        /// The assembly to use for instantiation.
        /// </summary>
        protected Assembly assembly;

        /// <summary>
        /// Constructs a new deserialization visitor.
        /// </summary>
        /// <param name="assembly">The assembly to use for object instantiation.</param>
        public DeserializationVisitor(Assembly assembly)
        {
            this.assembly = assembly;
            namespaces.Add("");
        }

        public override void VisitArray(ArrayNode array)
        {
            Array a = Array.CreateInstance(fields.Peek().FieldType.GetElementType(), array.Values.Length);
            objects.Push(a);
            for (int i = 0; i < a.Length; i++)
            {
                array.Values[i].Accept(this);
                object o = arrayElements.Pop();
                a.SetValue(o, i);
            }
            objects.Pop();

            if (currentObject is Array)
            {
                arrayElements.Push(a);
            }
            else
                fields.Peek().SetValue(currentObject, a);
        }

        public override void VisitField(FieldNode field)
        {
            FieldInfo info = currentObject.GetType().GetField(field.Name);
            if (info == null)
                return;
            // Don't set this field if it's got the [Skip] attribute
            if (info.GetCustomAttributes(typeof(SuperSerial.Attributes.SkipAttribute), false).Length != 0)
                return;
            fields.Push(info);
            field.Value.Accept(this);
            fields.Pop();
        }

        public override void VisitLiteral(LiteralNode literal)
        {
            if (currentObject is Array)
            {
                arrayElements.Push(literal.Value);
            }
            else
            {
                fields.Peek().SetValue(currentObject, literal.Value);
            }
        }

        public override void VisitObject(ObjectNode obj)
        {
            object reflectedObject = null;
            foreach(string ns in namespaces)
            {
                reflectedObject = assembly.CreateInstance(ns + obj.Type);
                if (reflectedObject != null) break;
            }
            if (reflectedObject == null)
            {
                throw new ParseException("Invalid type: " + obj.Type);
            }
            objects.Push(reflectedObject);
            foreach (FieldNode field in obj.Members)
            {
                field.Accept(this);
            }
            object o = objects.Pop();
            if (currentObject is Array)
                arrayElements.Push(o);
            else if (fields.Count > 0)
                fields.Peek().SetValue(currentObject, o);
            if (objects.Count == 0) Object = o;
        }

        public override void VisitBase(BaseNode node)
        {
            foreach(Node child in node.Values) 
            {
                child.Accept(this);
            }
        }

        public override void VisitNamespace(NamespaceNode node)
        {
            namespaces.Add(node.Namespace + ".");
        }
    }
}
